//phase 2 icon 
//scale_ratio = 1
//iconSize = 128,64
/**
 * @fileOverview Renders KML on the Google Maps JavaScript API Version 3
 * @name GeoXML3
 * @author Sterling Udell, Larry Ross, Brendan Byrd
 * @see http://code.google.com/p/geoxml3/
 *
 * geoxml3.js
 *
 * Renders KML on the Google Maps JavaScript API Version 3
 * http://code.google.com/p/geoxml3/
 *
 * Copyright 2010 Sterling Udell, Larry Ross
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
if (!String.prototype.trim) {
    /**
     * Remove leading and trailing whitespace.
     *
     * @augments String
     * @return {String}
     */
    String.prototype.trim = function() {
        return this.replace(/^\s+|\s+$/g, '');
    };
}
/**
 * @namespace The GeoXML3 namespace.
 */
geoXML3 = window.geoXML3 || {
    instances: []
};
/**
 * Constructor for the root KML parser object.
 *
 * <p>All top-level objects and functions are declared under a namespace of geoXML3.
 * The core object is geoXML3.parser; typically, you'll instantiate a one parser
 * per map.</p>
 *
 * @class Main XML parser.
 * @param {geoXML3.parserOptions} options
 */
geoXML3.parser = function(options) {
    // Private variables
    var parserOptions = new geoXML3.parserOptions(options);
    var docs = []; // Individual KML documents
    var docsByUrl = {}; // Same docs as an hash by cleanURL
    var kmzMetaData = {}; // Extra files from KMZ data
    var styles = {}; // Global list of styles
    var parserName;
    if (!parserOptions.infoWindow && parserOptions.singleInfoWindow) parserOptions.infoWindow = new google.maps.InfoWindow();
    var parseKmlString = function(kmlString, docSet) {
            // Internal values for the set of documents as a whole
            var internals = {
                parser: this,
                docSet: docSet || [],
                remaining: 1,
                parseOnly: !(parserOptions.afterParse || parserOptions.processStyles)
            };
            thisDoc = new Object();
            thisDoc.internals = internals;
            internals.docSet.push(thisDoc);
            render(geoXML3.xmlParse(kmlString), thisDoc);
        };
    var parse = function(urls, docSet) {
            // Process one or more KML documents
            if (!parserName) {
                parserName = 'geoXML3.instances[' + (geoXML3.instances.push(this) - 1) + ']';
            }
            if (typeof urls === 'string') {
                // Single KML document
                urls = [urls];
            }
            // Internal values for the set of documents as a whole
            var internals = {
                parser: this,
                docSet: docSet || [],
                remaining: urls.length,
                parseOnly: !(parserOptions.afterParse || parserOptions.processStyles)
            };
            var thisDoc, j;
            for (var i = 0; i < urls.length; i++) {
                var baseUrl = cleanURL(defileURL(location.pathname), urls[i]);
                if (docsByUrl[baseUrl]) {
                    // Reloading an existing document
                    thisDoc = docsByUrl[baseUrl];
                    thisDoc.reload = true;
                } else {
                    thisDoc = new Object();
                    thisDoc.baseUrl = baseUrl;
                    internals.docSet.push(thisDoc);
                }
                thisDoc.url = urls[i];
                thisDoc.internals = internals;
                fetchDoc(thisDoc.url, thisDoc);
            }
        };

    function fetchDoc(url, doc, resFunc) {
        resFunc = resFunc ||
        function(responseXML) {
            render(responseXML, doc);
        };
        if (typeof ZipFile === 'function' && typeof JSIO === 'object' && typeof JSIO.guessFileType === 'function') { // KMZ support requires these modules loaded
            contentType = JSIO.guessFileType(doc.baseUrl);
            if (contentType == JSIO.FileType.Binary || contentType == JSIO.FileType.Unknown) {
                doc.isCompressed = true;
                doc.baseDir = doc.baseUrl + '/';
                geoXML3.fetchZIP(url, resFunc, doc.internals.parser);
                return;
            }
        }
        doc.isCompressed = false;
        doc.baseDir = defileURL(doc.baseUrl);
        geoXML3.fetchXML(url, resFunc);
    }
    var hideDocument = function(doc) {
            if (!doc) doc = docs[0];
            // Hide the map objects associated with a document
            var i;
            if ( !! doc.markers) {
                for (i = 0; i < doc.markers.length; i++) {
                    if ( !! doc.markers[i].infoWindow) //doc.markers[i].infoWindow.close();
                    doc.markers[i].setMap(null);
                }
            }
            if ( !! doc.ggroundoverlays) {
                for (i = 0; i < doc.ggroundoverlays.length; i++) {
                    doc.ggroundoverlays[i].setOpacity(0);
                }
            }
            if ( !! doc.gpolylines) {
                for (i = 0; i < doc.gpolylines.length; i++) {
                    if ( !! doc.gpolylines[i].infoWindow) doc.gpolylines[i].infoWindow.close();
                    doc.gpolylines[i].setMap(null);
                }
            }
            if ( !! doc.gpolygons) {
                for (i = 0; i < doc.gpolygons.length; i++) {
                    if ( !! doc.gpolygons[i].infoWindow) doc.gpolygons[i].infoWindow.close();
                    doc.gpolygons[i].setMap(null);
                }
            }
        };
    var showDocument = function(doc) {
            if (!doc) doc = docs[0];
            // Show the map objects associated with a document
            var i;
            if ( !! doc.markers) {
                for (i = 0; i < doc.markers.length; i++) {
                    doc.markers[i].setVisible(true);
                }
            }
            if ( !! doc.ggroundoverlays) {
                for (i = 0; i < doc.ggroundoverlays.length; i++) {
                    doc.ggroundoverlays[i].setOpacity(doc.ggroundoverlays[i].percentOpacity_);
                }
            }
            if ( !! doc.gpolylines) {
                for (i = 0; i < doc.gpolylines.length; i++) {
                    doc.gpolylines[i].setMap(parserOptions.map);
                }
            }
            if ( !! doc.gpolygons) {
                for (i = 0; i < doc.gpolygons.length; i++) {
                    doc.gpolygons[i].setMap(parserOptions.map);
                }
            }
        };
    var defaultStyle = {
        balloon: {
            bgColor: 'ffffffff',
            textColor: 'ff000000',
            text: "<h3>$[name]</h3>\n<div>$[description]</div>\n<div>$[geDirections]</div>",
            displayMode: 'default'
        },
        icon: {
            scale: 0.1,
            dim: {
                x: 0,
                y: 0,
                w: -1,
                h: -1
            },
            hotSpot: {
                x: 0.5,
                y: 0.5,
                xunits: 'fraction',
                yunits: 'fraction'
            }
        },
        line: {
            color: 'ffffffff',
            // white (KML default)
            colorMode: 'normal',
            width: 1.0
        },
        poly: {
            color: 'ffffffff',
            // white (KML default)
            colorMode: 'normal',
            fill: true,
            outline: true
        }
    };
    var kmlNS = 'http://www.opengis.net/kml/2.2';
    var gxNS = 'http://www.google.com/kml/ext/2.2';
    var nodeValue = geoXML3.nodeValue;
    var getBooleanValue = geoXML3.getBooleanValue;
    var getElementsByTagNameNS = geoXML3.getElementsByTagNameNS;
    var getElementsByTagName = geoXML3.getElementsByTagName;

    function processStyleUrl(node) {
        var styleUrlStr = nodeValue(getElementsByTagName(node, 'styleUrl')[0]);
        if ( !! styleUrlStr && styleUrlStr.indexOf('#') != -1) var styleUrl = styleUrlStr.split('#');
        else var styleUrl = ["", ""];
        return styleUrl;
    }

    function processStyle(thisNode, baseUrl, styleID, baseDir) {
        var style = (baseUrl === '{inline}') ? clone(defaultStyle) : (styles[baseUrl][styleID] = styles[baseUrl][styleID] || clone(defaultStyle));
        var styleNodes = getElementsByTagName(thisNode, 'BalloonStyle');
        if ( !! styleNodes && styleNodes.length > 0) {
            style.balloon.bgColor = nodeValue(getElementsByTagName(styleNodes[0], 'bgColor')[0], style.balloon.bgColor);
            style.balloon.textColor = nodeValue(getElementsByTagName(styleNodes[0], 'textColor')[0], style.balloon.textColor);
            style.balloon.text = nodeValue(getElementsByTagName(styleNodes[0], 'text')[0], style.balloon.text);
            style.balloon.displayMode = nodeValue(getElementsByTagName(styleNodes[0], 'displayMode')[0], style.balloon.displayMode);
        }
        // style.list = (unsupported; doesn't make sense in Google Maps)
        var styleNodes = getElementsByTagName(thisNode, 'IconStyle');
        if ( !! styleNodes && styleNodes.length > 0) {
            var icon = style.icon;
            icon.scale = parseFloat(nodeValue(getElementsByTagName(styleNodes[0], 'scale')[0], icon.scale));
            // style.icon.heading   = (unsupported; not supported in API)
            // style.icon.color     = (unsupported; not supported in API)
            // style.icon.colorMode = (unsupported; not supported in API)
            styleNodes = getElementsByTagName(thisNode, 'Icon');
            if ( !! styleNodes && styleNodes.length > 0) {
                icon.href = nodeValue(getElementsByTagName(styleNodes[0], 'href')[0]);
                icon.url = cleanURL(baseDir, icon.href);
                // Detect images buried in KMZ files (and use a base64 encoded URL)
                if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl;
                // Support for icon palettes and exact size dimensions
                icon.dim = {
                    x: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'x')[0], icon.dim.x)),
                    y: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'y')[0], icon.dim.y)),
                    w: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'w')[0], icon.dim.w)),
                    h: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'h')[0], icon.dim.h))
                };
                styleNodes = getElementsByTagName(thisNode, 'hotSpot');
                if ( !! styleNodes && styleNodes.length > 0) {
                    icon.hotSpot = {
                        x: styleNodes[0].getAttribute('x'),
                        y: styleNodes[0].getAttribute('y'),
                        xunits: styleNodes[0].getAttribute('xunits'),
                        yunits: styleNodes[0].getAttribute('yunits')
                    };
                }
                // certain occasions where we need the pixel size of the image (like the default settings...)
                // (NOTE: Scale is applied to entire image, not just the section of the icon palette.  So,
                //  if we need scaling, we'll need the img dimensions no matter what.)
                if ((icon.dim.w < 0 || icon.dim.h < 0) && (icon.xunits != 'pixels' || icon.yunits == 'fraction') || icon.scale != 1.0) {
                    // (hopefully, this will load by the time we need it...)
                    icon.img = new Image();
                    icon.img.onload = function() {
                        if (icon.dim.w < 0 || icon.dim.h < 0) {
                            icon.dim.w = this.width;
                            icon.dim.h = this.height;
                        }
                    };
                    icon.img.src = icon.url;
                    // sometimes the file is already cached and it never calls onLoad
                    if (icon.img.width > 0) {
                        icon.dim.w = icon.img.width;
                        icon.dim.h = icon.img.height;
                    }
                }
            }
        }
        // style.label = (unsupported; may be possible but not with API)
        styleNodes = getElementsByTagName(thisNode, 'LineStyle');
        if ( !! styleNodes && styleNodes.length > 0) {
            style.line.color = nodeValue(getElementsByTagName(styleNodes[0], 'color')[0], style.line.color);
            style.line.colorMode = nodeValue(getElementsByTagName(styleNodes[0], 'colorMode')[0], style.line.colorMode);
            style.line.width = nodeValue(getElementsByTagName(styleNodes[0], 'width')[0], style.line.width);
            // style.line.outerColor      = (unsupported; not supported in API)
            // style.line.outerWidth      = (unsupported; not supported in API)
            // style.line.physicalWidth   = (unsupported; unneccesary in Google Maps)
            // style.line.labelVisibility = (unsupported; possible to implement)
        }
        styleNodes = getElementsByTagName(thisNode, 'PolyStyle');
        if ( !! styleNodes && styleNodes.length > 0) {
            style.poly.color = nodeValue(getElementsByTagName(styleNodes[0], 'color')[0], style.poly.color);
            style.poly.colorMode = nodeValue(getElementsByTagName(styleNodes[0], 'colorMode')[0], style.poly.colorMode);
            style.poly.outline = getBooleanValue(getElementsByTagName(styleNodes[0], 'outline')[0], style.poly.outline);
            style.poly.fill = getBooleanValue(getElementsByTagName(styleNodes[0], 'fill')[0], style.poly.fill);
        }
        return style;
    }
    // from http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-a-javascript-object
    // http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone

    function clone(obj) {
        if (obj == null || typeof(obj) != 'object') return obj;
        if (obj.cloneNode) return obj.cloneNode(true);
        var temp = new obj.constructor();
        for (var key in obj) temp[key] = clone(obj[key]);
        return temp;
    }

    function processStyleMap(thisNode, baseUrl, styleID, baseDir) {
        var pairs = getElementsByTagName(thisNode, 'Pair');
        var map = new Object();
        // add each key to the map
        for (var pr = 0; pr < pairs.length; pr++) {
            var pairKey = nodeValue(getElementsByTagName(pairs[pr], 'key')[0]);
            var pairStyle = nodeValue(getElementsByTagName(pairs[pr], 'Style')[0]);
            var pairStyleUrl = processStyleUrl(pairs[pr]);
            var pairStyleBaseUrl = pairStyleUrl[0] ? cleanURL(baseDir, pairStyleUrl[0]) : baseUrl;
            var pairStyleID = pairStyleUrl[1];
            if ( !! pairStyle) {
                map[pairKey] = processStyle(pairStyle, pairStyleBaseUrl, pairStyleID);
            } else if ( !! pairStyleID && !! styles[pairStyleBaseUrl][pairStyleID]) {
                map[pairKey] = clone(styles[pairStyleBaseUrl][pairStyleID]);
            }
        }
        if ( !! map["normal"]) {
            styles[baseUrl][styleID] = clone(map["normal"]);
        } else {
            styles[baseUrl][styleID] = clone(defaultStyle);
        }
        if ( !! map["highlight"] && !! parserOptions.processStyles) {
            processStyleID(map["highlight"]);
        }
        styles[baseUrl][styleID].map = clone(map);
    }

    function processPlacemarkCoords(node, tag) {
        var parent = getElementsByTagName(node, tag);
        var coordListA = [];
        for (var i = 0; i < parent.length; i++) {
            var coordNodes = getElementsByTagName(parent[i], 'coordinates');
            if (!coordNodes) {
                if (coordListA.length > 0) {
                    break;
                } else {
                    return [{
                        coordinates: []
                    }];
                }
            }
            for (var j = 0; j < coordNodes.length; j++) {
                var coords = nodeValue(coordNodes[j]).trim();
                coords = coords.replace(/,\s+/g, ',');
                var path = coords.split(/\s+/g);
                var pathLength = path.length;
                var coordList = [];
                for (var k = 0; k < pathLength; k++) {
                    coords = path[k].split(',');
                    if (!isNaN(coords[0]) && !isNaN(coords[1])) {
                        coordList.push({
                            lat: parseFloat(coords[1]),
                            lng: parseFloat(coords[0]),
                            alt: parseFloat(coords[2])
                        });
                    }
                }
                coordListA.push({
                    coordinates: coordList
                });
            }
        }
        return coordListA;
    }
    var render = function(responseXML, doc) {
            // Callback for retrieving a KML document: parse the KML and display it on the map
            if (!responseXML) {
                // Error retrieving the data
                geoXML3.log('Unable to retrieve ' + doc.url);
                if (parserOptions.failedParse) parserOptions.failedParse(doc);
                doc.failed = true;
                return;
            } else if (responseXML.parseError && responseXML.parseError.errorCode != 0) {
                // IE parse error
                var err = responseXML.parseError;
                var msg = 'Parse error in line ' + err.line + ', col ' + err.linePos + ' (error code: ' + err.errorCode + ")\n" + "\nError Reason: " + err.reason + 'Error Line: ' + err.srcText;
                geoXML3.log('Unable to retrieve ' + doc.url + ': ' + msg);
                if (parserOptions.failedParse) parserOptions.failedParse(doc);
                doc.failed = true;
                return;
            } else if (responseXML.documentElement && responseXML.documentElement.nodeName == 'parsererror') {
                // Firefox parse error
                geoXML3.log('Unable to retrieve ' + doc.url + ': ' + responseXML.documentElement.childNodes[0].nodeValue);
                if (parserOptions.failedParse) parserOptions.failedParse(doc);
                doc.failed = true;
                return;
            } else if (!doc) {
                throw 'geoXML3 internal error: render called with null document';
            } else { //no errors
                var i;
                doc.placemarks = [];
                doc.groundoverlays = [];
                doc.ggroundoverlays = [];
                doc.networkLinks = [];
                doc.gpolygons = [];
                doc.gpolylines = [];
                // Check for dependent KML files
                var nodes = getElementsByTagName(responseXML, 'styleUrl');
                var docSet = doc.internals.docSet;
                for (var i = 0; i < nodes.length; i++) {
                    var url = nodeValue(nodes[i]).split('#')[0];
                    if (!url) continue; // #id (inside doc)
                    var rUrl = cleanURL(doc.baseDir, url);
                    if (rUrl === doc.baseUrl) continue; // self
                    if (docsByUrl[rUrl]) continue; // already loaded
                    var thisDoc;
                    var j = docSet.indexOfObjWithItem('baseUrl', rUrl);
                    if (j != -1) {
                        // Already listed to be loaded, but probably in the wrong order.
                        // Load it right away to immediately resolve dependency.
                        thisDoc = docSet[j];
                        if (thisDoc.failed) continue; // failed to load last time; don't retry it again
                    } else {
                        // Not listed at all; add it in
                        thisDoc = new Object();
                        thisDoc.url = rUrl; // url can't be trusted inside KMZ files, since it may .. outside of the archive
                        thisDoc.baseUrl = rUrl;
                        thisDoc.internals = doc.internals;
                        doc.internals.docSet.push(thisDoc);
                        doc.internals.remaining++;
                    }
                    // render dependent KML first then re-run renderer
                    fetchDoc(rUrl, thisDoc, function(thisResXML) {
                        render(thisResXML, thisDoc);
                        render(responseXML, doc);
                    });
                    // to prevent cross-dependency issues, just load the one
                    // file first and re-check the rest later
                    return;
                }
                // Parse styles
                doc.styles = styles[doc.baseUrl] = styles[doc.baseUrl] || {};
                var styleID, styleNodes;
                nodes = getElementsByTagName(responseXML, 'Style');
                nodeCount = nodes.length;
                for (i = 0; i < nodeCount; i++) {
                    thisNode = nodes[i];
                    var styleID = thisNode.getAttribute('id');
                    if ( !! styleID) processStyle(thisNode, doc.baseUrl, styleID, doc.baseDir);
                }
                // Parse StyleMap nodes
                nodes = getElementsByTagName(responseXML, 'StyleMap');
                for (i = 0; i < nodes.length; i++) {
                    thisNode = nodes[i];
                    var styleID = thisNode.getAttribute('id');
                    if ( !! styleID) processStyleMap(thisNode, doc.baseUrl, styleID, doc.baseDir);
                }
                if ( !! parserOptions.processStyles || !parserOptions.createMarker) {
                    // Convert parsed styles into GMaps equivalents
                    processStyles(doc);
                }
                // Parse placemarks
                if ( !! doc.reload && !! doc.markers) {
                    for (i = 0; i < doc.markers.length; i++) {
                        doc.markers[i].active = false;
                    }
                }
                var placemark, node, coords, path, marker, poly;
                var placemark, coords, path, pathLength, marker, polygonNodes, coordList;
                var placemarkNodes = getElementsByTagName(responseXML, 'Placemark');
                for (pm = 0; pm < placemarkNodes.length; pm++) {
                    // Init the placemark object
                    node = placemarkNodes[pm];
                    var styleUrl = processStyleUrl(node);
                    placemark = {
                        name: nodeValue(getElementsByTagName(node, 'name')[0]),
                        description: nodeValue(getElementsByTagName(node, 'description')[0]),
                        styleUrl: styleUrl.join('#'),
                        styleBaseUrl: styleUrl[0] ? cleanURL(doc.baseDir, styleUrl[0]) : doc.baseUrl,
                        styleID: styleUrl[1],
                        visibility: getBooleanValue(getElementsByTagName(node, 'visibility')[0], true),
                        balloonVisibility: getBooleanValue(getElementsByTagNameNS(node, gxNS, 'balloonVisibility')[0], !parserOptions.suppressInfoWindows)
                    };
                    placemark.style = (styles[placemark.styleBaseUrl] && styles[placemark.styleBaseUrl][placemark.styleID]) || clone(defaultStyle);
                    // inline style overrides shared style
                    var inlineStyles = getElementsByTagName(node, 'Style');
                    if (inlineStyles && (inlineStyles.length > 0)) {
                        var style = processStyle(node, '{inline}', '{inline}');
                        processStyleID(style);
                        if (style) placemark.style = style;
                    }
                    if (/^https?:\/\//.test(placemark.description)) {
                        placemark.description = ['<a href="', placemark.description, '">', placemark.description, '</a>'].join('');
                    }
                    // record list of variables for substitution
                    placemark.vars = {
                        display: {
                            name: 'Name',
                            description: 'Description',
                            address: 'Street Address',
                            id: 'ID',
                            Snippet: 'Snippet',
                            geDirections: 'Directions'
                        },
                        val: {
                            name: placemark.name || '',
                            description: placemark.description || '',
                            address: nodeValue(getElementsByTagName(node, 'address')[0], ''),
                            id: node.getAttribute('id') || '',
                            Snippet: nodeValue(getElementsByTagName(node, 'Snippet')[0], '')
                        },
                        directions: ['f=d', 'source=GeoXML3']
                    };
                    // add extended data to variables
                    var extDataNodes = getElementsByTagName(node, 'ExtendedData');
                    if ( !! extDataNodes && extDataNodes.length > 0) {
                        var dataNodes = getElementsByTagName(extDataNodes[0], 'Data');
                        for (var d = 0; d < dataNodes.length; d++) {
                            var dn = dataNodes[d];
                            var name = dn.getAttribute('name');
                            if (!name) continue;
                            var dName = nodeValue(getElementsByTagName(dn, 'displayName')[0], name);
                            var val = nodeValue(getElementsByTagName(dn, 'value')[0]);
                            placemark.vars.val[name] = val;
                            placemark.vars.display[name] = dName;
                        }
                    }
                    // process MultiGeometry
                    var GeometryNodes = getElementsByTagName(node, 'coordinates');
                    var Geometry = null;
                    if ( !! GeometryNodes && (GeometryNodes.length > 0)) {
                        for (var gn = 0; gn < GeometryNodes.length; gn++) {
                            if (GeometryNodes[gn].parentNode && GeometryNodes[gn].parentNode.nodeName) {
                                var GeometryPN = GeometryNodes[gn].parentNode;
                                Geometry = GeometryPN.nodeName;
                                // Extract the coordinates
                                // What sort of placemark?
                                switch (Geometry) {
                                case "Point":
                                    placemark.Point = processPlacemarkCoords(node, "Point")[0];
                                    placemark.latlng = new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng);
                                    pathLength = 1;
                                    break;
                                case "LinearRing":
                                    // Polygon/line
                                    polygonNodes = getElementsByTagName(node, 'Polygon');
                                    // Polygon
                                    if (!placemark.Polygon) placemark.Polygon = [{
                                        outerBoundaryIs: {
                                            coordinates: []
                                        },
                                        innerBoundaryIs: [{
                                            coordinates: []
                                        }]
                                    }];
                                    for (var pg = 0; pg < polygonNodes.length; pg++) {
                                        placemark.Polygon[pg] = {
                                            outerBoundaryIs: {
                                                coordinates: []
                                            },
                                            innerBoundaryIs: [{
                                                coordinates: []
                                            }]
                                        };
                                        placemark.Polygon[pg].outerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "outerBoundaryIs");
                                        placemark.Polygon[pg].innerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "innerBoundaryIs");
                                    }
                                    coordList = placemark.Polygon[0].outerBoundaryIs;
                                    break;
                                case "LineString":
                                    pathLength = 0;
                                    placemark.LineString = processPlacemarkCoords(node, "LineString");
                                    break;
                                default:
                                    break;
                                }
                            }
                        }
                    }
                    // call the custom placemark parse function if it is defined
                    if ( !! parserOptions.pmParseFn) parserOptions.pmParseFn(node, placemark);
                    doc.placemarks.push(placemark);
                    // single marker
                    if (placemark.Point) {
                        if ( !! google.maps) {
                            doc.bounds = doc.bounds || new google.maps.LatLngBounds();
                            doc.bounds.extend(placemark.latlng);
                        }
                        // Potential user-defined marker handler
                        var pointCreateFunc = parserOptions.createMarker || createMarker;
                        var found = false;
                        if (!parserOptions.createMarker) {
                            // Check to see if this marker was created on a previous load of this document
                            if ( !! doc) {
                                doc.markers = doc.markers || [];
                                if (doc.reload) {
                                    for (var j = 0; j < doc.markers.length; j++) {
                                        if (doc.markers[j].getPosition().equals(placemark.latlng)) {
                                            found = doc.markers[j].active = true;
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                        if (!found) {
                            // Call the marker creator
                            var marker = pointCreateFunc(placemark, doc);
                            if (marker) marker.active = placemark.visibility;
                        }
                    }
                    // polygon/line
                    var poly, line;
                    if ( !! doc) {
                        if (placemark.Polygon) doc.gpolygons = doc.gpolygons || [];
                        if (placemark.LineString) doc.gpolylines = doc.gpolylines || [];
                    }
                    var polyCreateFunc = parserOptions.createPolygon || createPolygon;
                    var lineCreateFunc = parserOptions.createLineString || createPolyline;
                    if (placemark.Polygon) {
                        poly = polyCreateFunc(placemark, doc);
                        if (poly) poly.active = placemark.visibility;
                    }
                    if (placemark.LineString) {
                        line = lineCreateFunc(placemark, doc);
                        if (line) line.active = placemark.visibility;
                    }
                    if ( !! google.maps) {
                        doc.bounds = doc.bounds || new google.maps.LatLngBounds();
                        if (poly) doc.bounds.union(poly.bounds);
                        if (line) doc.bounds.union(line.bounds);
                    }
                } // placemark loop
                if ( !! doc.reload && !! doc.markers) {
                    for (i = doc.markers.length - 1; i >= 0; i--) {
                        if (!doc.markers[i].active) {
                            if ( !! doc.markers[i].infoWindow) {
                                doc.markers[i].infoWindow.close();
                            }
                            doc.markers[i].setMap(null);
                            doc.markers.splice(i, 1);
                        }
                    }
                }
                // Parse ground overlays
                if ( !! doc.reload && !! doc.groundoverlays) {
                    for (i = 0; i < doc.groundoverlays.length; i++) {
                        doc.groundoverlays[i].active = false;
                    }
                }
                if ( !! doc) {
                    doc.groundoverlays = doc.groundoverlays || [];
                }
                // doc.groundoverlays =[];
                var groundOverlay, color, transparency, overlay;
                var groundNodes = getElementsByTagName(responseXML, 'GroundOverlay');
                for (i = 0; i < groundNodes.length; i++) {
                    node = groundNodes[i];
                    // Detect images buried in KMZ files (and use a base64 encoded URL)
                    var gnUrl = cleanURL(doc.baseDir, nodeValue(getElementsByTagName(node, 'href')[0]));
                    if (kmzMetaData[gnUrl]) gnUrl = kmzMetaData[gnUrl].dataUrl;
                    // Init the ground overlay object
                    groundOverlay = {
                        name: nodeValue(getElementsByTagName(node, 'name')[0]),
                        description: nodeValue(getElementsByTagName(node, 'description')[0]),
                        icon: {
                            href: gnUrl
                        },
                        latLonBox: {
                            north: parseFloat(nodeValue(getElementsByTagName(node, 'north')[0])),
                            east: parseFloat(nodeValue(getElementsByTagName(node, 'east')[0])),
                            south: parseFloat(nodeValue(getElementsByTagName(node, 'south')[0])),
                            west: parseFloat(nodeValue(getElementsByTagName(node, 'west')[0]))
                        }
                    };
                    if ( !! google.maps) {
                        doc.bounds = doc.bounds || new google.maps.LatLngBounds();
                        doc.bounds.union(new google.maps.LatLngBounds(
                        new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west), new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)));
                    }
                    // Opacity is encoded in the color node
                    var colorNode = getElementsByTagName(node, 'color');
                    if (colorNode && colorNode.length > 0) {
                        groundOverlay.opacity = geoXML3.getOpacity(nodeValue(colorNode[0]));
                    } else {
                        groundOverlay.opacity = 1.0; // KML default
                    }
                    doc.groundoverlays.push(groundOverlay);
                    if ( !! parserOptions.createOverlay) {
                        // User-defined overlay handler
                        parserOptions.createOverlay(groundOverlay, doc);
                    } else {
                        // Check to see if this overlay was created on a previous load of this document
                        var found = false;
                        if ( !! doc) {
                            doc.groundoverlays = doc.groundoverlays || [];
                            if (doc.reload) {
                                overlayBounds = new google.maps.LatLngBounds(
                                new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west), new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east));
                                var overlays = doc.groundoverlays;
                                for (i = overlays.length; i--;) {
                                    if ((overlays[i].bounds().equals(overlayBounds)) && (overlays.url_ === groundOverlay.icon.href)) {
                                        found = overlays[i].active = true;
                                        break;
                                    }
                                }
                            }
                        }
                        if (!found) {
                            // Call the built-in overlay creator
                            overlay = createOverlay(groundOverlay, doc);
                            overlay.active = true;
                        }
                    }
                    if ( !! doc.reload && !! doc.groundoverlays && !! doc.groundoverlays.length) {
                        var overlays = doc.groundoverlays;
                        for (i = overlays.length; i--;) {
                            if (!overlays[i].active) {
                                overlays[i].remove();
                                overlays.splice(i, 1);
                            }
                        }
                        doc.groundoverlays = overlays;
                    }
                }
                // Parse network links
                var networkLink;
                var docPath = document.location.pathname.split('/');
                docPath = docPath.splice(0, docPath.length - 1).join('/');
                var linkNodes = getElementsByTagName(responseXML, 'NetworkLink');
                for (i = 0; i < linkNodes.length; i++) {
                    node = linkNodes[i];
                    // Init the network link object
                    networkLink = {
                        name: nodeValue(getElementsByTagName(node, 'name')[0]),
                        link: {
                            href: nodeValue(getElementsByTagName(node, 'href')[0]),
                            refreshMode: nodeValue(getElementsByTagName(node, 'refreshMode')[0])
                        }
                    };
                    // Establish the specific refresh mode
                    if (!networkLink.link.refreshMode) {
                        networkLink.link.refreshMode = 'onChange';
                    }
                    if (networkLink.link.refreshMode === 'onInterval') {
                        networkLink.link.refreshInterval = parseFloat(nodeValue(getElementsByTagName(node, 'refreshInterval')[0]));
                        if (isNaN(networkLink.link.refreshInterval)) {
                            networkLink.link.refreshInterval = 0;
                        }
                    } else if (networkLink.link.refreshMode === 'onChange') {
                        networkLink.link.viewRefreshMode = nodeValue(getElementsByTagName(node, 'viewRefreshMode')[0]);
                        if (!networkLink.link.viewRefreshMode) {
                            networkLink.link.viewRefreshMode = 'never';
                        }
                        if (networkLink.link.viewRefreshMode === 'onStop') {
                            networkLink.link.viewRefreshTime = nodeValue(getElementsByTagName(node, 'refreshMode')[0]);
                            networkLink.link.viewFormat = nodeValue(getElementsByTagName(node, 'refreshMode')[0]);
                            if (!networkLink.link.viewFormat) {
                                networkLink.link.viewFormat = 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]';
                            }
                        }
                    }
                    if (!/^[\/|http]/.test(networkLink.link.href)) {
                        // Fully-qualify the HREF
                        networkLink.link.href = docPath + '/' + networkLink.link.href;
                    }
                    // Apply the link
                    if ((networkLink.link.refreshMode === 'onInterval') && (networkLink.link.refreshInterval > 0)) {
                        // Reload at regular intervals
                        setInterval(parserName + '.parse("' + networkLink.link.href + '")', 1000 * networkLink.link.refreshInterval);
                    } else if (networkLink.link.refreshMode === 'onChange') {
                        if (networkLink.link.viewRefreshMode === 'never') {
                            // Load the link just once
                            doc.internals.parser.parse(networkLink.link.href, doc.internals.docSet);
                        } else if (networkLink.link.viewRefreshMode === 'onStop') {
                            // Reload when the map view changes
                        }
                    }
                }
            }
            if ( !! doc.bounds) {
                doc.internals.bounds = doc.internals.bounds || new google.maps.LatLngBounds();
                doc.internals.bounds.union(doc.bounds);
            }
            if ( !! doc.markers || !! doc.groundoverlays || !! doc.gpolylines || !! doc.gpolygons) {
                doc.internals.parseOnly = false;
            }
            if (!doc.internals.parseOnly) {
                // geoXML3 is not being used only as a real-time parser, so keep the processed documents around
                if (!docsByUrl[doc.baseUrl]) {
                    docs.push(doc);
                    docsByUrl[doc.baseUrl] = doc;
                } else {
                    // internal replacement, which keeps the same memory ref loc in docs and docsByUrl
                    for (var i in docsByUrl[doc.baseUrl]) {
                        docsByUrl[doc.baseUrl][i] = doc[i];
                    }
                }
            }
            doc.internals.remaining--;
            if (doc.internals.remaining === 0) {
                // We're done processing this set of KML documents
                // Options that get invoked after parsing completes
                if (parserOptions.zoom && !! doc.internals.bounds && !doc.internals.bounds.isEmpty() && !! parserOptions.map) {
                    //parserOptions.map.fitBounds(doc.internals.bounds);
                }
                if (parserOptions.afterParse) {
                    parserOptions.afterParse(doc.internals.docSet);
                }
            }
        };
    var kmlColor = function(kmlIn, colorMode) {
            var kmlColor = {};
            kmlIn = kmlIn || 'ffffffff'; // white (KML 2.2 default)
            var aa = kmlIn.substr(0, 2);
            var bb = kmlIn.substr(2, 2);
            var gg = kmlIn.substr(4, 2);
            var rr = kmlIn.substr(6, 2);
            kmlColor.opacity = parseInt(aa, 16) / 256;
            kmlColor.color = (colorMode === 'random') ? randomColor(rr, gg, bb) : '#' + rr + gg + bb;
            return kmlColor;
        };
    // Implemented per KML 2.2 <ColorStyle> specs
    var randomColor = function(rr, gg, bb) {
            var col = {
                rr: rr,
                gg: gg,
                bb: bb
            };
            for (var k in col) {
                var v = col[k];
                if (v == null) v = 'ff';
                // RGB values are limiters for random numbers (ie: 7f would be a random value between 0 and 7f)
                v = Math.round(Math.random() * parseInt(rr, 16)).toString(16);
                if (v.length === 1) v = '0' + v;
                col[k] = v;
            }
            return '#' + col.rr + col.gg + col.bb;
        };
    var processStyleID = function(style) {
            var icon = style.icon;
            if (!icon.href) return;
            if (icon.img && !icon.img.complete && (icon.dim.w < 0) && (icon.dim.h < 0)) {
                // we're still waiting on the image loading (probably because we've been blocking since the declaration)
                // so, let's queue this function on the onload stack
                icon.markerBacklog = [];
                icon.img.onload = function() {
                    if (icon.dim.w < 0 || icon.dim.h < 0) {
                        icon.dim.w = this.width;
                        icon.dim.h = this.height;
                    }
                    processStyleID(style);
                    // we will undoubtedly get some createMarker queuing, so set this up in advance
                    for (var i = 0; i < icon.markerBacklog.length; i++) {
                        var p = icon.markerBacklog[i][0];
                        var d = icon.markerBacklog[i][1];
                        createMarker(p, d);
                        if (p.marker) p.marker.active = true;
                    }
                    delete icon.markerBacklog;
                };
                return;
            } else if (icon.dim.w < 0 || icon.dim.h < 0) {
                if (icon.img && icon.img.complete) {
                    // sometimes the file is already cached and it never calls onLoad
                    icon.dim.w = icon.img.width;
                    icon.dim.h = icon.img.height;
                } else {
                    // settle for a default of 32x32
                    icon.dim.whGuess = true;
                    icon.dim.w = 32;
                    icon.dim.h = 32;
                }
            }
            // pre-scaled variables
            var rnd = Math.round;
            //var scale_ratio = 0.1;
            var scale_ratio = 1;  //phase 2 icon
            var scaled = {
                x: icon.dim.x * scale_ratio,
                y: icon.dim.y * scale_ratio,
                w: icon.dim.w * scale_ratio,
                h: icon.dim.h * scale_ratio,
                aX: icon.hotSpot.x * scale_ratio,
                aY: icon.hotSpot.y * scale_ratio,
                iW: (icon.img ? icon.img.width : icon.dim.w) * scale_ratio,
                iH: (icon.img ? icon.img.height : icon.dim.h) * scale_ratio
            };
            // Figure out the anchor spot
            var aX, aY;
            switch (icon.hotSpot.xunits) {
            case 'fraction':
                aX = rnd(scaled.aX * icon.dim.w);
                break;
            case 'insetPixels':
                aX = rnd(icon.dim.w * icon.scale - scaled.aX);
                break;
            default:
                aX = rnd(scaled.aX);
                break; // already pixels
            }
            aY = rnd(((icon.hotSpot.yunits === 'fraction') ? icon.dim.h : 1) * scaled.aY); // insetPixels Y = pixels Y
            var iconAnchor = new google.maps.Point(aX, aY);
            // Sizes
            // (NOTE: Scale is applied to entire image, not just the section of the icon palette.)
            //phase 2 icon
			var iconSize = icon.dim.whGuess ? null : new google.maps.Size(128, 64);  
            //var iconSize = icon.dim.whGuess ? null : new google.maps.Size(64, 64);
            var iconScale = scale_ratio == 1.0 ? null : icon.dim.whGuess ? new google.maps.Size(rnd(scaled.w), rnd(scaled.h)) : new google.maps.Size(rnd(scaled.iW), rnd(scaled.iH));
            var iconOrigin = new google.maps.Point(rnd(scaled.x), rnd(scaled.y));
            // Detect images buried in KMZ files (and use a base64 encoded URL)
            if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl;
            // Init the style object with the KML icon
            icon.marker = new google.maps.MarkerImage(
            icon.url, // url
            iconSize, // size
            iconOrigin, // origin
            iconAnchor, // anchor
            iconScale // scaledSize
            );
            // Look for a predictable shadow
            var stdRegEx = /\/(red|blue|green|yellow|lightblue|purple|pink|orange)(-dot)?\.png/;
            var shadowSize = new google.maps.Size(59, 32);
            var shadowPoint = new google.maps.Point(16, 32);
            if (stdRegEx.test(icon.href)) {
                // A standard GMap-style marker icon
                icon.shadow = new google.maps.MarkerImage('http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png', // url
                shadowSize, // size
                null, // origin
                shadowPoint, // anchor
                shadowSize // scaledSize
                );
            } else if (icon.href.indexOf('-pushpin.png') > -1) {
                // Pushpin marker icon
                icon.shadow = new google.maps.MarkerImage('http://maps.google.com/mapfiles/ms/micons/pushpin_shadow.png', // url
                shadowSize, // size
                null, // origin
                shadowPoint, // anchor
                shadowSize // scaledSize
                );
            }
/* else {
      // Other MyMaps KML standard icon
      icon.shadow = new google.maps.MarkerImage(
        icon.href.replace('.png', '.shadow.png'),                        // url
        shadowSize,                                                      // size
        null,                                                            // origin
        anchorPoint,                                                     // anchor
        shadowSize                                                       // scaledSize
      );
    } */
        };
    var processStyles = function(doc) {
		for (var styleID in doc.styles) {
			processStyleID(doc.styles[styleID]);
		}
	};
    var createMarker = function(placemark, doc) {
            // create a Marker to the map from a placemark KML object
		var icon = placemark.style.icon;
		if (!icon.marker && icon.img) {
			// yay, single point of failure is holding up multiple markers...
			icon.markerBacklog = icon.markerBacklog || [];
			icon.markerBacklog.push([placemark, doc]);
			return;
		}
		// Load basic marker properties
		var markerOptions = geoXML3.combineOptions(parserOptions.markerOptions, {
			map: parserOptions.map,
			position: new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng),
			title: placemark.name,
			zIndex: Math.round(placemark.Point.coordinates[0].lat * -100000) << 5,
			icon: icon.marker,
			shadow: icon.shadow,
			flat: !icon.shadow,
			visible: placemark.visibility
		});
		// Create the marker on the map
		var marker = new google.maps.Marker(markerOptions);
		if ( !! doc) doc.markers.push(marker);
		
		// Set up and create the infowindow if it is not suppressed
		createInfoWindow2(placemark, doc, marker);
		placemark.marker = marker;
		cluster.unshift(marker); 					//add marker to cluster array for refresh
		AddMarkerToCluster(marker);
		//console.log("adding markers to cluster : ", marker);
		return marker;
    };
	
    var createOverlay = function(groundOverlay, doc) {
            // Add a ProjectedOverlay to the map from a groundOverlay KML object
            if (!window.ProjectedOverlay) {
                throw 'geoXML3 error: ProjectedOverlay not found while rendering GroundOverlay from KML';
            }
            var bounds = new google.maps.LatLngBounds(
            new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west), new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east));
            var overlayOptions = geoXML3.combineOptions(parserOptions.overlayOptions, {
                percentOpacity: groundOverlay.opacity * 100
            });
            var overlay = new ProjectedOverlay(parserOptions.map, groundOverlay.icon.href, bounds, overlayOptions);
            if ( !! doc) {
                doc.ggroundoverlays = doc.ggroundoverlays || [];
                doc.ggroundoverlays.push(overlay);
            }
            return overlay;
        };
    // Create Polyline
    var createPolyline = function(placemark, doc) {
            var path = [];
            var bounds = new google.maps.LatLngBounds();
            for (var j = 0; j < placemark.LineString.length; j++) {
                var coords = placemark.LineString[j].coordinates;
                for (var i = 0; i < coords.length; i++) {
                    var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
                    path.push(pt);
                    bounds.extend(pt);
                }
            }
            // point to open the infowindow if triggered
            var point = path[Math.floor(path.length / 2)];
            // Load basic polyline properties
            var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode);
            var polyOptions = geoXML3.combineOptions(parserOptions.polylineOptions, {
                map: parserOptions.map,
                path: path,
                strokeColor: kmlStrokeColor.color,
                strokeWeight: placemark.style.line.width,
                strokeOpacity: kmlStrokeColor.opacity,
                title: placemark.name,
                visible: placemark.visibility
            });
            var p = new google.maps.Polyline(polyOptions);
            p.bounds = bounds;
            // setup and create the infoWindow if it is not suppressed
            createInfoWindow2(placemark, doc, p);
            if ( !! doc) doc.gpolylines.push(p);
            placemark.polyline = p;
            return p;
        };
        // Create Polygon
    var createPolygon = function(placemark, doc) {
            var bounds = new google.maps.LatLngBounds();
            var pathsLength = 0;
            var paths = [];
            for (var polygonPart = 0; polygonPart < placemark.Polygon.length; polygonPart++) {
                for (var j = 0; j < placemark.Polygon[polygonPart].outerBoundaryIs.length; j++) {
                    var coords = placemark.Polygon[polygonPart].outerBoundaryIs[j].coordinates;
                    var path = [];
                    for (var i = 0; i < coords.length; i++) {
                        var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
                        path.push(pt);
                        bounds.extend(pt);
                    }
                    paths.push(path);
                    pathsLength += path.length;
                }
                for (var j = 0; j < placemark.Polygon[polygonPart].innerBoundaryIs.length; j++) {
                    var coords = placemark.Polygon[polygonPart].innerBoundaryIs[j].coordinates;
                    var path = [];
                    for (var i = 0; i < coords.length; i++) {
                        var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
                        path.push(pt);
                        bounds.extend(pt);
                    }
                    paths.push(path);
                    pathsLength += path.length;
                }
            }
            // Load basic polygon properties
            var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode);
            var kmlFillColor = kmlColor(placemark.style.poly.color, placemark.style.poly.colorMode);
            if (!placemark.style.poly.fill) kmlFillColor.opacity = 0.0;
            var strokeWeight = placemark.style.line.width;
            if (!placemark.style.poly.outline) {
                strokeWeight = 0;
                kmlStrokeColor.opacity = 0.0;
            }
            var polyOptions = geoXML3.combineOptions(parserOptions.polygonOptions, {
                map: parserOptions.map,
                paths: paths,
                title: placemark.name,
                strokeColor: kmlStrokeColor.color,
                strokeWeight: strokeWeight,
                strokeOpacity: kmlStrokeColor.opacity,
                fillColor: kmlFillColor.color,
                fillOpacity: kmlFillColor.opacity,
                visible: placemark.visibility
            });
            var p = new google.maps.Polygon(polyOptions);
            p.bounds = bounds;
            createInfoWindow2(placemark, doc, p);
            if ( !! doc) doc.gpolygons.push(p);
            placemark.polygon = p;
            return p;
        };
	
    var createInfoWindow = function(placemark, doc, gObj) {
            var bStyle = placemark.style.balloon;
            var vars = placemark.vars;
            if (!placemark.balloonVisibility || bStyle.displayMode === 'hide') return;
            var text = "<table border='1'>";
            // define geDirections
			//if (placemark.latlng) {
			//		console.log(placemark);
			//	  vars.directions.push('sll=' + placemark.latlng.toUrlValue());
//
//				  var url = 'http://maps.google.com/maps?' + vars.directions.join('&');
//				  var address = encodeURIComponent( vars.val.address || placemark.latlng.toUrlValue() ).replace(/\%20/g, '+');
//
//				  vars.val.geDirections = '<a href="' + url + '&daddr=' + address + '" target=_blank>To Here</a> - <a href="' + url + '&saddr=' + address + '" target=_blank>From Here</a>';
//				  
//				}
//				else vars.val.geDirections = '';
//			
            vars.val.geDirections = '';
            for (x in vars.display) {
                if (vars.val[x] != "") {
                    text += "<tr><td>" + vars.display[x] + "</td><td>" + vars.val[x] + "</td></tr>";
					//text += vars.display[x] + "  :  " + vars.val[x] + '</br>';
                }
            }
			text += "</table>";
			console.log(text);
            // add in the variables
            var iwText = bStyle.text.replace(/\$\[(\w+(\/displayName)?)\]/g, function(txt, n, dn) {
                return dn ? vars.display[n] : vars.val[n];
            });
            var classTxt = 'geoxml3_infowindow geoxml3_style_' + placemark.styleID;
            // color styles
            var styleArr = [];
            if (bStyle.bgColor != 'ffffffff') styleArr.push('background: ' + kmlColor(bStyle.bgColor).color + ';');
            if (bStyle.textColor != 'ff000000') styleArr.push('color: ' + kmlColor(bStyle.textColor).color + ';');
            var styleProp = styleArr.length ? ' style="' + styleArr.join(' ') + '"' : '';
            var infoWindowOptions = geoXML3.combineOptions(parserOptions.infoWindowOptions, {
                //content: '<div class="' + classTxt + '"' + styleProp + '>' + text + '</div>',
                content: text,
                pixelOffset: new google.maps.Size(0, 2)
            });
            gObj.infoWindow = parserOptions.infoWindow || new google.maps.InfoWindow(infoWindowOptions);
            gObj.infoWindowOptions = infoWindowOptions;
            // Info Window-opening event handler
            google.maps.event.addListener(gObj, 'click', function(e) {
                var iW = this.infoWindow;
                iW.close();
                iW.setOptions(this.infoWindowOptions);
                if (e && e.latLng) iW.setPosition(e.latLng);
                else if (this.bounds) iW.setPosition(this.bounds.getCenter());
                iW.setContent("<div id='geoxml3_infowindow'>" + iW.getContent() + "</div>");
                google.maps.event.addListenerOnce(iW, "domready", function() {
                    var node = document.getElementById('geoxml3_infowindow');
                    var imgArray = node.getElementsByTagName('img');
                    for (var i = 0; i < imgArray.length; i++) {
                        var imgUrlIE = imgArray[i].getAttribute("src");
                        var imgUrl = cleanURL(doc.baseDir, imgUrlIE);
                        if (kmzMetaData[imgUrl]) {
                            imgArray[i].src = kmzMetaData[imgUrl].dataUrl;
                        } else if (kmzMetaData[imgUrlIE]) {
                            imgArray[i].src = kmzMetaData[imgUrlIE].dataUrl;
                        }
                    }
                });
                iW.open(this.map, this.bounds ? null : this);
            });
			console.log("infowindow created");
        }

	// Create custom infowindow
	var createInfoWindow2 = function(placemark, doc, gObj) {
		var bStyle = placemark.style.balloon;
		var vars = placemark.vars;
		if (!placemark.balloonVisibility || bStyle.displayMode === 'hide') return;
		var text = "<div id='table' ><table border='1'>";
		vars.val.geDirections = '';
		var index = 0;
		for (x in vars.display) {
                    if (vars.display[x] == "รูปภาพ1")
                    {
                        if (index % 2 == 0)
                                text += "<tr class = 'evenrowcolor'><td>รูปภาพ</td><td>" + vars.val[x] + "</td></tr>";
                            else	
                                text += "<tr class = 'oddrowcolor'><td>รูปภาพ</td><td>" + vars.val[x] + "</td></tr>";
                            index++; 
                    }
                    else if (vars.display[x] == "รูปภาพ2")
                    {
                        text+= "<span style='display:none'>" + vars.val[x] + "</span>";
                         
                    }
                    else if (vars.display[x] == "รูปภาพ3")
                    {
                        text+= "<span style='display:none'>" + vars.val[x] + "</span>";
                         
                    }
                    else if (vars.display[x] == "รูปภาพ4")
                    {
                        text+= "<span style='display:none'>" + vars.val[x] + "</span>";
                         
                    }
                    else if (vars.display[x] == "รูปภาพ5")
                    {
                        text+= "<span style='display:none'>" + vars.val[x] + "</span>";
                         
                    }
                    else if (vars.val[x] != "" && vars.display[x] != "Name" && vars.display[x] != "ลำดับ" && vars.display[x] != "ความคลาดเคลื่อน") 
                    {
                        if (index % 2 == 0)
                            text += "<tr class = 'evenrowcolor'><td>" + vars.display[x] + "</td><td>" + vars.val[x] + "</td></tr>";
                        else	
                            text += "<tr class = 'oddrowcolor'><td>" + vars.display[x] + "</td><td>" + vars.val[x] + "</td></tr>";
                        index++;
                    }
		}
		text += "</table></div>";
		//console.log(text);
		// add in the variables
		var iwText = bStyle.text.replace(/\$\[(\w+(\/displayName)?)\]/g, function(txt, n, dn) {
			return dn ? vars.display[n] : vars.val[n];
		});
		var classTxt = 'geoxml3_infowindow geoxml3_style_' + placemark.styleID;
		// color styles
		var styleArr = [];
		if (bStyle.bgColor != 'ffffffff') styleArr.push('background: ' + kmlColor(bStyle.bgColor).color + ';');
		if (bStyle.textColor != 'ff000000') styleArr.push('color: ' + kmlColor(bStyle.textColor).color + ';');
		var styleProp = styleArr.length ? ' style="' + styleArr.join(' ') + '"' : '';
		var infoWindowOptions = geoXML3.combineOptions(parserOptions.infoWindowOptions, {
			//content: '<div class="' + classTxt + '"' + styleProp + '>' + text + '</div>',
			content: text,
			pixelOffset: new google.maps.Size(0, 2)
		});
		gObj.infoWindow = parserOptions.infoWindow || new google.maps.InfoWindow(infoWindowOptions);
		gObj.infoWindowOptions = infoWindowOptions;
		  
		
		
		var myOptions = {
			content: text,
			disableAutoPan: false,
			maxWidth: 0,
			pixelOffset: new google.maps.Size(-140, 0),
			zIndex: null,
			boxStyle: {
				background: "url('http://google-maps-utility-library-v3.googlecode.com/svn/trunk/infobox/examples/tipbox.gif') no-repeat",
				opacity: 1,
				width: "300px"
			},
			closeBoxMargin: "-5px 0px 0px 0",
			closeBoxURL: "image/close.png",
			infoBoxClearance: new google.maps.Size(1, 1),
			isHidden: false,
			pane: "floatPane",
			enableEventPropagation: false
		};
		var ib = new InfoBox(myOptions);
		gObj.ib = ib;
		//console.log("ib: ", ib);
		// Info Window-opening event handler
		google.maps.event.addListener(gObj, 'click', function(e) {
                    if (reporter_ib != null)
                        reporter_ib.setMap(null);
                    gObj.ib.open(map, this);
                    reporter_ib = gObj.ib;
                    google.maps.event.addListener(gObj.ib, 'domready', function(){
                            $("a[href$='.jpg']").attr( "rel", "prettyPhoto[camera]" );
                            $("a[href$='.jpg']").attr( "data-role", "none" );
                            //console.log($("a[href$='.jpg']"), $("a[href$='.jpg']").prop( 'rel' ));
                            $("a[rel^='prettyPhoto']:lt(5)").prettyPhoto();
                    });
                    
		});
	};
    return {
        // Expose some properties and methods
        options: parserOptions,
        docs: docs,
        docsByUrl: docsByUrl,
        kmzMetaData: kmzMetaData,
        parse: parse,
        render: render,
        parseKmlString: parseKmlString,
        hideDocument: hideDocument,
        showDocument: showDocument,
        processStyles: processStyles,
        createMarker: createMarker,
        createOverlay: createOverlay,
        createPolyline: createPolyline,
        createPolygon: createPolygon
    };
};

// End of KML Parser
// Helper objects and functions
geoXML3.getOpacity = function(kmlColor) {
    // Extract opacity encoded in a KML color value. Returns a number between 0 and 1.
    if ( !! kmlColor && (kmlColor !== '') && (kmlColor.length == 8)) {
        var transparency = parseInt(kmlColor.substr(0, 2), 16);
        return transparency / 255;
    } else {
        return 1;
    }
};
// Log a message to the debugging console, if one exists
geoXML3.log = function(msg) {
    if ( !! window.console) {
        console.log(msg);
    } else {
        alert("log:" + msg);
    }
};
/**
 * Creates a new parserOptions object.
 * @class GeoXML3 parser options.
 * @param {Object} overrides Any options you want to declare outside of the defaults should be included here.
 * @property {google.maps.Map} map The API map on which geo objects should be rendered.
 * @property {google.maps.MarkerOptions} markerOptions If the parser is adding Markers to the map itself, any options specified here will be applied to them.
 * @property {google.maps.InfoWindowOptions} infoWindowOptions If the parser is adding Markers to the map itself, any options specified here will be applied to their attached InfoWindows.
 * @property {ProjectedOverlay.options} overlayOptions If the parser is adding ProjectedOverlays to the map itself, any options specified here will be applied to them.
 */
geoXML3.parserOptions = function(overrides) {
    this.map = null,
    /** If true, the parser will automatically move the map to a best-fit of the geodata after parsing of a KML document completes.
     * @type Boolean
     * @default true
     */
    this.zoom = false,
    /**#@+ @type Boolean
     *     @default false */
    /** If true, only a single Marker created by the parser will be able to have its InfoWindow open at once (simulating the behavior of GMaps API v2). */
    this.singleInfoWindow = false, /** If true, suppresses the rendering of info windows. */
    this.suppressInfoWindows = false,
    /**
     * Control whether to process styles now or later.
     *
     * <p>By default, the parser only processes KML &lt;Style&gt; elements into their GMaps equivalents
     * if it will be creating its own Markers (the createMarker option is null). Setting this option
     * to true will force such processing to happen anyway, useful if you're going to be calling parser.createMarker
     * yourself later. OTOH, leaving this option false removes runtime dependency on the GMaps API, enabling
     * the use of geoXML3 as a standalone KML parser.</p>
     */
    this.processStyles = false, /**#@-*/
    this.markerOptions = {}, this.infoWindowOptions = {}, this.overlayOptions = {}, /**#@+ @event */
    /** This function will be called when parsing of a KML document is complete.
     * @param {geoXML3.parser#docs} doc Parsed KML data. */
    this.afterParse = null,
    /** This function will be called when parsing of a KML document is complete.
     * @param {geoXML3.parser#docs} doc Parsed KML data. */
    this.failedParse = null,
    /**
     * If supplied, this function will be called once for each marker <Placemark> in the KML document, instead of the parser adding its own Marker to the map.
     * @param {geoXML3.parser.render#placemark} placemark Placemark object.
     * @param {geoXML3.parser#docs} doc Parsed KML data.
     */
    this.createMarker = null,
    /**
     * If supplied, this function will be called once for each <GroundOverlay> in the KML document, instead of the parser adding its own ProjectedOverlay to the map.
     * @param {geoXML3.parser.render#groundOverlay} groundOverlay GroundOverlay object.
     * @param {geoXML3.parser#docs} doc Parsed KML data.
     */
    this.createOverlay = null ;/**#@-*/
    if (overrides) {
        for (var prop in overrides) {
            if (overrides.hasOwnProperty(prop)) this[prop] = overrides[prop];
        }
    }
    return this;
};
/**
 * Combine two options objects: a set of default values and a set of override values.
 *
 * @deprecated This has been replaced with {@link geoXML3.parserOptions#combineOptions}.
 * @param {geoXML3.parserOptions|Object} overrides Override values.
 * @param {geoXML3.parserOptions|Object} defaults Default values.
 * @return {geoXML3.parserOptions} Combined result.
 */
geoXML3.combineOptions = function(overrides, defaults) {
    var result = {};
    if ( !! overrides) {
        for (var prop in overrides) {
            if (overrides.hasOwnProperty(prop)) result[prop] = overrides[prop];
        }
    }
    if ( !! defaults) {
        for (prop in defaults) {
            if (defaults.hasOwnProperty(prop) && result[prop] === undefined) result[prop] = defaults[prop];
        }
    }
    return result;
};
/**
 * Combine two options objects: a set of default values and a set of override values.
 *
 * @function
 * @param {geoXML3.parserOptions|Object} overrides Override values.
 * @param {geoXML3.parserOptions|Object} defaults Default values.
 * @return {geoXML3.parserOptions} Combined result.
 */
geoXML3.parserOptions.prototype.combineOptions = geoXML3.combineOptions;
// Retrieve an XML document from url and pass it to callback as a DOM document
geoXML3.fetchers = [];
/**
 * Parses a XML string.
 *
 * <p>Parses the given XML string and returns the parsed document in a
 * DOM data structure. This function will return an empty DOM node if
 * XML parsing is not supported in this browser.</p>
 *
 * @param {String} str XML string.
 * @return {Element|Document} DOM.
 */
geoXML3.xmlParse = function(str) {
    if (typeof ActiveXObject != 'undefined' && typeof GetObject != 'undefined') {
        var doc = new ActiveXObject('Microsoft.XMLDOM');
        doc.loadXML(str);
        return doc;
    }
    if (typeof DOMParser != 'undefined') {
        return (new DOMParser()).parseFromString(str, 'text/xml');
    }
    return createElement('div', null);
};
/**
 * Fetches a XML document.
 *
 * <p>Fetches/parses the given XML URL and passes the parsed document (in a
 * DOM data structure) to the given callback.  Documents are downloaded
 * and parsed asynchronously.</p>
 *
 * @param {String} url URL of XML document.  Must be uncompressed XML only.
 * @param {Function(Document)} callback Function to call when the document is processed.
 */
geoXML3.fetchXML = function(url, callback) {
    function timeoutHandler() {
        callback();
    };
    var xhrFetcher = new Object();
    if ( !! geoXML3.fetchers.length) xhrFetcher = geoXML3.fetchers.pop();
    else if ( !! window.XMLHttpRequest) xhrFetcher.fetcher = new window.XMLHttpRequest(); // Most browsers
    else if ( !! window.ActiveXObject) { // Some IE
        // the many versions of IE's XML fetchers
        var AXOs = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.5.0', 'MSXML2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP', 'MSXML.XMLHTTP'];
        for (var i = 0; i < AXOs.length; i++) {
            try {
                xhrFetcher.fetcher = new ActiveXObject(AXOs[i]);
                break;
            } catch (e) {
                continue;
            }
        }
        if (!xhrFetcher.fetcher) {
            geoXML3.log('Unable to create XHR object');
            callback(null);
            return null;
        }
    }
    if ( !! xhrFetcher.fetcher.overrideMimeType) xhrFetcher.fetcher.overrideMimeType('text/xml');
    xhrFetcher.fetcher.open('GET', url, true);
    xhrFetcher.fetcher.onreadystatechange = function() {
        if (xhrFetcher.fetcher.readyState === 4) {
            // Retrieval complete
            if ( !! xhrFetcher.xhrtimeout) clearTimeout(xhrFetcher.xhrtimeout);
            if (xhrFetcher.fetcher.status >= 400) {
                geoXML3.log('HTTP error ' + xhrFetcher.fetcher.status + ' retrieving ' + url);
                callback();
            }
            // Returned successfully
            else {
                if (xhrFetcher.fetcher.responseXML) {
                    // Sometimes IE will get the data, but won't bother loading it as an XML doc
                    var xmlDoc = xhrFetcher.fetcher.responseXML;
                    if (xmlDoc && !xmlDoc.documentElement && !xmlDoc.ownerElement) xmlDoc.loadXML(xhrFetcher.fetcher.responseText);
                    callback(xmlDoc);
                } else // handle valid xml sent with wrong MIME type 
                callback(geoXML3.xmlParse(xhrFetcher.fetcher.responseText));
            }
            
        }
    };
    xhrFetcher.xhrtimeout = setTimeout(timeoutHandler, 60000);
    xhrFetcher.fetcher.send(null);
    return null;
};
var IEversion = function() {
        // http://msdn.microsoft.com/workshop/author/dhtml/overview/browserdetection.asp
        // Returns the version of Internet Explorer or a -1
        // (indicating the use of another browser).
        var rv = -1; // Return value assumes failure
        if (navigator.appName == 'Microsoft Internet Explorer') {
            var ua = navigator.userAgent;
            var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
            if (re.exec(ua) != null) {
                rv = parseFloat(RegExp.$1);
            }
        }
        return rv;
    };
/**
 * Fetches a KMZ document.
 *
 * <p>Fetches/parses the given ZIP URL, parses each image file, and passes
 * the parsed KML document to the given callback.  Documents are downloaded
 * and parsed asynchronously, though the KML file is always passed after the
 * images have been processed, in case the callback requires the image data.</p>
 *
 * @requires ZipFile.complete.js
 * @param {String} url URL of KMZ document.  Must be a valid KMZ/ZIP archive.
 * @param {Function(Document)} callback Function to call when the document is processed.
 * @param {geoXML3.parser} parser A geoXML3.parser object.  This is used to populate the KMZ image data.
 * @author Brendan Byrd
 * @see http://code.google.com/apis/kml/documentation/kmzarchives.html
 */
geoXML3.fetchZIP = function(url, callback, parser) {
    // Just need a single 'new' declaration with a really long function...
    var zipFile = new ZipFile(url, function(zip) {
        // Retrieval complete
        // Check for ERRORs in zip.status
        for (var i = 0; i < zip.status.length; i++) {
            var msg = zip.status[i];
            if (msg.indexOf("ERROR") == 0) {
                geoXML3.log('HTTP/ZIP error retrieving ' + url + ': ' + msg);
                callback();
                return;
            } else if (msg.indexOf("WARNING") == 0) { // non-fatal, but still might be useful
                geoXML3.log('HTTP/ZIP warning retrieving ' + url + ': ' + msg);
            }
        }
        // Make sure KMZ structure is according to spec (with a single KML file in the root dir)
        var KMLCount = 0;
        var KML;
        for (var i = 0; i < zip.entries.length; i++) {
            var name = zip.entries[i].name;
            if (!/\.kml$/.test(name)) continue;
            KMLCount++;
            if (KMLCount == 1) KML = i;
            else {
                geoXML3.log('KMZ warning retrieving ' + url + ': found extra KML "' + name + '" in KMZ; discarding...');
            }
        }
        // Returned successfully, but still needs extracting
        var baseUrl = cleanURL(defileURL(url), url) + '/';
        var kmlProcessing = { // this is an object just so it gets passed properly
            timer: null,
            extractLeft: 0,
            timerCalls: 0
        };
        var extractCb = function(entry, entryContent) {
                var mdUrl = cleanURL(baseUrl, entry.name);
                var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase();
                kmlProcessing.extractLeft--;
                if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) {
                    geoXML3.log('KMZ error extracting ' + mdUrl + ': ' + entryContent.description);
                    callback();
                    return;
                }
                // MIME types that can be used in KML
                var mime;
                if (ext === 'jpg') ext = 'jpeg';
                if (/^(gif|jpeg|png)$/.test(ext)) mime = 'image/' + ext;
                else if (ext === 'mp3') mime = 'audio/mpeg';
                else if (ext === 'm4a') mime = 'audio/mp4';
                else if (ext === 'm4a') mime = 'audio/MP4-LATM';
                else mime = 'application/octet-stream';
                parser.kmzMetaData[mdUrl] = {};
                parser.kmzMetaData[mdUrl].entry = entry;
                // ...
                parser.kmzMetaData[mdUrl].dataUrl = 'data:' + mime + ';base64,' + base64Encode(entryContent);
                // IE cannot handle GET requests beyond 2071 characters, even if it's an inline image
                if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
                    if (((IEversion() < 8.0) && (parser.kmzMetaData[mdUrl].dataUrl.length > 2071)) || ((IEversion < 9.0) && (parser.kmzMetaData[mdUrl].dataUrl.length > 32767))) {
                        parser.kmzMetaData[mdUrl].dataUrl =
                        // this is a simple IE icon; to hint at the problem...
                        '' + 'oGDMgzSsiyGCAhCETDPMh5XQCBwYBrNBIKWmg0MCQHj8MJU5IoroYCY6AAAgrDIbbQDGIK6DR5UPhlNo0JAlSUNAiDgH7eNAxEDWAKCQM2AAFheVxYAA0AIkFOJ1gBcQQaUQKKA5w7LpcEBwkJaKMUEQA7';
                    }
                }
                parser.kmzMetaData[internalSrc(entry.name)] = parser.kmzMetaData[mdUrl];
            };
        var kmlExtractCb = function(entry, entryContent) {
                if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) {
                    geoXML3.log('KMZ error extracting ' + mdUrl + ': ' + entryContent.description);
                    callback();
                    return;
                }
                // check to see if the KML is the last file extracted
                clearTimeout(kmlProcessing.timer);
                if (kmlProcessing.extractLeft <= 1) {
                    kmlProcessing.extractLeft--;
                    callback(geoXML3.xmlParse(entryContent));
                    return;
                } else {
                    // KML file isn't last yet; it may need to use those files, so wait a bit (100ms)
                    kmlProcessing.timerCalls++;
                    if (kmlProcessing.timerCalls < 100) {
                        kmlProcessing.timer = setTimeout(function() {
                            kmlExtractCb(entry, entryContent);
                        }, 100);
                    } else {
                        geoXML3.log('KMZ warning extracting ' + url + ': entire ZIP has not been extracted after 10 seconds; running through KML, anyway...');
                        kmlProcessing.extractLeft--;
                        callback(geoXML3.xmlParse(entryContent));
                    }
                }
                return;
            };
        for (var i = 0; i < zip.entries.length; i++) {
            var entry = zip.entries[i];
            var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase();
            if (!/^(gif|jpe?g|png|kml)$/.test(ext)) continue; // not going to bother to extract files we don't support
            if (ext === "kml" && i != KML) continue; // extra KMLs get discarded
            if (!parser && ext != "kml") continue; // cannot store images without a parser object
            // extract asynchronously
            kmlProcessing.extractLeft++;
            if (ext === "kml") entry.extract(kmlExtractCb);
            else entry.extract(extractCb);
        }
    });
};
/**
 * Extract the text value of a DOM node, with leading and trailing whitespace trimmed.
 *
 * @param {Element} node XML node/element.
 * @param {Any} delVal Default value if the node doesn't exist.
 * @return {String|Null}
 */
geoXML3.nodeValue = function(node, defVal) {
    var retStr = "";
    if (!node) {
        return (typeof defVal === 'undefined' || defVal === null) ? null : defVal;
    }
    if (node.nodeType == 3 || node.nodeType == 4 || node.nodeType == 2) {
        retStr += node.nodeValue;
    } else if (node.nodeType == 1 || node.nodeType == 9 || node.nodeType == 11) {
        for (var i = 0; i < node.childNodes.length; ++i) {
            retStr += arguments.callee(node.childNodes[i]);
        }
    }
    return retStr;
};
/**
 * Loosely translate various values of a DOM node to a boolean.
 *
 * @param {Element} node XML node/element.
 * @param {Boolean} delVal Default value if the node doesn't exist.
 * @return {Boolean|Null}
 */
geoXML3.getBooleanValue = function(node, defVal) {
    var nodeContents = geoXML3.nodeValue(node);
    if (nodeContents === null) return defVal || false;
    nodeContents = parseInt(nodeContents);
    if (isNaN(nodeContents)) return true;
    if (nodeContents == 0) return false;
    else return true;
};
/**
 * Browser-normalized version of getElementsByTagNameNS.
 *
 * <p>Required because IE8 doesn't define it.</p>
 *
 * @param {Element|Document} node DOM object.
 * @param {String} namespace Full namespace URL to search against.
 * @param {String} tagname XML local tag name.
 * @return {Array of Elements}
 * @author Brendan Byrd
 */
geoXML3.getElementsByTagNameNS = function(node, namespace, tagname) {
    if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagNameNS(namespace, tagname);
    if (!node) return [];
    var root = node.documentElement || node.ownerDocument && node.ownerDocument.documentElement;
    if (!root || !root.attributes) return [];
    // search for namespace prefix
    for (var i = 0; i < root.attributes.length; i++) {
        var attr = root.attributes[i];
        if (attr.prefix === 'xmlns' && attr.nodeValue === namespace) return node.getElementsByTagName(attr.baseName + ':' + tagname);
        else if (attr.nodeName === 'xmlns' && attr.nodeValue === namespace) {
            // default namespace
            if (typeof node.selectNodes != 'undefined') {
                // Newer IEs have the SelectionNamespace property that can be used with selectNodes
                if (!root.ownerDocument.getProperty('SelectionNamespaces')) root.ownerDocument.setProperty('SelectionNamespaces', "xmlns:defaultNS='" + namespace + "'");
                return node.selectNodes('.//defaultNS:' + tagname);
            } else {
                // Otherwise, you can still try to tack on the 'xmlns' attribute to root
                root.setAttribute('xmlns:defaultNS', namespace);
                return node.getElementsByTagName('defaultNS:' + tagname);
            }
        }
    }
    return geoXML3.getElementsByTagName(node, tagname); // try the unqualified version
};
/**
 * Browser-normalized version of getElementsByTagName.
 *
 * <p>Required because MSXML 6.0 will treat this function as a NS-qualified function,
 * despite the missing NS parameter.</p>
 *
 * @param {Element|Document} node DOM object.
 * @param {String} tagname XML local tag name.
 * @return {Array of Elements}
 * @author Brendan Byrd
 */
geoXML3.getElementsByTagName = function(node, tagname) {
    if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagName(tagname); // if it has both functions, it should be accurate
    //  if (node && typeof node.selectNodes != 'undefined')            return node.selectNodes(".//*[local-name()='" + tagname + "']");
    return node.getElementsByTagName(tagname); // hope for the best...
};
/**
 * Turn a directory + relative URL into an absolute one.
 *
 * @private
 * @param {String} d Base directory.
 * @param {String} s Relative URL.
 * @return {String} Absolute URL.
 * @author Brendan Byrd
 */
var toAbsURL = function(d, s) {
        var p, f, i;
        var h = location.protocol + "://" + location.host;
        if (!s.length) return '';
        if (/^\w+:/.test(s)) return s;
        if (s.indexOf('/') == 0) return h + s;
        p = d.replace(/\/[^\/]*$/, '');
        f = s.match(/\.\.\//g);
        if (f) {
            s = s.substring(f.length * 3);
            for (i = f.length; i--;) {
                p = p.substring(0, p.lastIndexOf('/'));
            }
        }
        return h + p + '/' + s;
    };
var internalSrc = function(src) {
        //this gets the full url
        var url = document.location.href;
        //this removes everything after the last slash in the path
        url = url.substring(0, url.lastIndexOf("/") + 1);
        var internalPath = url + src;
        return internalPath;
    };
    /**
     * Remove current host from URL
     *
     * @private
     * @param {String} s Absolute or relative URL.
     * @return {String} Root-based relative URL.
     * @author Brendan Byrd
     */
var dehostURL = function(s) {
        var h = location.protocol + "://" + location.host;
        h = h.replace(/([\.\\\+\*\?\[\^\]\$\(\)])/g, '\\$1'); // quotemeta
        return s.replace(new RegExp('^' + h, 'i'), '');
    };
    /**
     * Removes all query strings, #IDs, '../' references, and
     * hosts from a URL.
     *
     * @private
     * @param {String} d Base directory.
     * @param {String} s Absolute or relative URL.
     * @return {String} Root-based relative URL.
     * @author Brendan Byrd
     */
var cleanURL = function(d, s) {
        return dehostURL(toAbsURL(d ? d.split('#')[0].split('?')[0] : defileURL(location.pathname), s ? s.split('#')[0].split('?')[0] : ''));
    };
    /**
     * Remove filename from URL
     *
     * @private
     * @param {String} s Relative URL.
     * @return {String} Base directory.
     * @author Brendan Byrd
     */
var defileURL = function(s) {
        return s ? s.substr(0, s.lastIndexOf('/') + 1) : '/';
    };
    // Some extra Array subs for ease of use
    // http://stackoverflow.com/questions/143847/best-way-to-find-an-item-in-a-javascript-array
    Array.prototype.hasObject = (!Array.indexOf ?
    function(obj) {
        var l = this.length + 1;
        while (l--) {
            if (this[l - 1] === obj) return true;
        }
        return false;
    } : function(obj) {
        return (this.indexOf(obj) !== -1);
    });
Array.prototype.hasItemInObj = function(name, item) {
    var l = this.length + 1;
    while (l--) {
        if (this[l - 1][name] === item) return true;
    }
    return false;
};
if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(obj, fromIndex) {
        if (fromIndex == null) {
            fromIndex = 0;
        } else if (fromIndex < 0) {
            fromIndex = Math.max(0, this.length + fromIndex);
        }
        for (var i = fromIndex, j = this.length; i < j; i++) {
            if (this[i] === obj) return i;
        }
        return -1;
    };
}
Array.prototype.indexOfObjWithItem = function(name, item, fromIndex) {
    if (fromIndex == null) {
        fromIndex = 0;
    } else if (fromIndex < 0) {
        fromIndex = Math.max(0, this.length + fromIndex);
    }
    for (var i = fromIndex, j = this.length; i < j; i++) {
        if (this[i][name] === item) return i;
    }
    return -1;
};
/**
 * Borrowed from jquery.base64.js, with some "Array as input" corrections
 *
 * @private
 * @param {Array of charCodes} input An array of byte ASCII codes (0-255).
 * @return {String} A base64-encoded string.
 * @author Brendan Byrd
 */
var base64Encode = function(input) {
        var keyString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;
        while (i < input.length) {
            chr1 = input[i++];
            chr2 = input[i++];
            chr3 = input[i++];
            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;
            if (chr2 == undefined) enc3 = enc4 = 64;
            else if (chr3 == undefined) enc4 = 64;
            output = output + keyString.charAt(enc1) + keyString.charAt(enc2) + keyString.charAt(enc3) + keyString.charAt(enc4);
        }
        return output;
    };